import tensorflow as tf
import tensorflow_datasets as tfds
import numpy as np
import matplotlib.pyplot as plt
import os
import plotly.graph_objects as go
gpus = tf.config.list_physical_devices('GPU')
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu,True)
Reove dodgy images¶
import cv2
import imghdr
F:\temporary\temp\ipykernel_3864\4232469594.py:2: DeprecationWarning: 'imghdr' is deprecated and slated for removal in Python 3.13 import imghdr
training_data_dir = 'data/training'
testing_data_dir = 'data/testing'
img_extensions = ["jpg", "jpeg", "bmp", "png"]
for image_dir in os.listdir(training_data_dir):
for image in os.listdir(os.path.join(training_data_dir, image_dir)):
image_path = os.path.join(training_data_dir, image_dir, image)
try:
img = cv2.imread(image_path)
ext = imghdr.what(image_path)
if ext not in img_extensions:
print('image not in ext list {}'.format(img_extensions))
os.remove(image_path)
except Exception as e:
print("issue with image {}".format(image_path))
Loading the data¶
import numpy as np
import matplotlib.pyplot as plt
classes = ['glioma_tumor', 'meningioma_tumor', 'no_tumor', 'pituitary_tumor']
import tensorflow as tf
from tensorflow.keras.utils import to_categorical
# Load your data
training_data = tf.keras.utils.image_dataset_from_directory(
training_data_dir,
labels='inferred',
label_mode='int',
class_names=classes,
seed=123,
batch_size=32,
image_size=(256, 256)
)
testing_data = tf.keras.utils.image_dataset_from_directory(
testing_data_dir,
labels='inferred',
label_mode='int',
class_names=classes,
seed=123,
batch_size=32,
image_size=(256, 256)
)
Found 2870 files belonging to 4 classes. Found 394 files belonging to 4 classes.
data_iterator = training_data.as_numpy_iterator()
plotting some images¶
def plot_images_with_labels(images, labels, class_names, num_images=10, rows=2, figsize=(10, 5)):
if num_images > len(images):
num_images = len(images)
fig, axes = plt.subplots(rows, num_images // rows, figsize=figsize)
for i, ax in enumerate(axes.flat):
ax.imshow(images[i].numpy().astype("uint8"))
ax.set_title(class_names[labels[i]], fontsize=10)
ax.axis('off')
ax.tick_params(axis='both', which='both', length=0)
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.set_xlabel(class_names[labels[i]], fontsize=8)
plt.tight_layout()
plt.show()
batch = data_iterator.next()
data_iterator = iter(training_data) # Initialize an iterator for the dataset
batch = next(data_iterator) # Retrieve a batch of data
class_names = ['glioma_tumor', 'meningioma_tumor', 'no_tumor', 'pituitary_tumor'] # Define the class names
plot_images_with_labels(batch[0][:10], batch[1][:10], class_names)
batch[0].shape
TensorShape([32, 256, 256, 3])
batch[1].shape
TensorShape([32])
splitting the data¶
train_size = int(len(training_data)*.8)
validation_size = int(len(training_data)*.2)
train_size
72
validation_size
18
train = training_data.take(train_size)
val = training_data.skip(train_size).take(validation_size)
test = testing_data.take(len(testing_data))
train_images, train_labels = [], []
for images, labels in train:
train_images.append(images.numpy()) # Assuming you're using TensorFlow datasets
train_labels.append(labels.numpy())
# Convert list to numpy array
train_images = np.concatenate(train_images)
train_labels = np.concatenate(train_labels)
# Do the same for validation data
val_images, val_labels = [], []
for images, labels in val:
val_images.append(images.numpy())
val_labels.append(labels.numpy())
val_images = np.concatenate(val_images)
val_labels = np.concatenate(val_labels)
test_images, test_labels = [], []
for images, labels in test:
test_images.append(images.numpy())
test_labels.append(labels.numpy())
test_images = np.concatenate(test_images)
test_labels = np.concatenate(test_labels)
# Convert integer labels to one-hot encoded vectors
num_classes = 4
train_labels_one_hot = tf.keras.utils.to_categorical(train_labels, num_classes=num_classes)
val_labels_one_hot = tf.keras.utils.to_categorical(val_labels, num_classes=num_classes)
test_labels_one_hot = tf.keras.utils.to_categorical(test_labels, num_classes=num_classes)
Building model and explanation¶
from tensorflow.keras.models import Sequential
from tensorflow.keras.regularizers import l2
from keras import regularizers
from keras.layers import BatchNormalization
from tensorflow.keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D, Dense, Flatten, Dropout, Activation,Input
fonctions d'activation (ReLu)¶
Les fonctions d'activation jouent un rôle crucial dans les réseaux de neurones artificiels. Elles introduisent la non-linéarité nécessaire pour que le réseau puisse apprendre des modèles complexes et réaliser des tâches variées, comme la classification d'images ou la reconnaissance vocale. Sans ces fonctions, les réseaux de neurones se comporteraient comme des modèles linéaires, limitant ainsi leur capacité à résoudre des problèmes sophistiqués.
Parmi les différentes fonctions d'activation, la fonction ReLU (Rectified Linear Unit) est particulièrement populaire. La ReLU est définie par la formule $$ f(x) = \max(0, x) $$ , ce qui signifie qu'elle renvoie zéro si l'entrée est négative et la valeur de l'entrée elle-même si elle est positive. Cette simplicité computationnelle la rend très efficace. De plus, elle aide à atténuer le problème du gradient évanescent, facilitant ainsi l'apprentissage des réseaux profonds. Cependant, la ReLU peut souffrir du problème des neurones "morts" lorsque des valeurs négatives permanentes rendent certains neurones inactifs pendant l'entraînement. Malgré ce défaut, la ReLU reste largement utilisée en raison de ses nombreux avantages.

model = Sequential()
model.add(Conv2D(16, (3,3), 1, activation='relu', input_shape=(256,256,3), kernel_regularizer=l2(0.01)))
model.add(BatchNormalization())
model.add(MaxPooling2D())
model.add(Conv2D(32, (3,3), 1, activation='relu',kernel_regularizer=l2(0.05)))
model.add(BatchNormalization())
model.add(MaxPooling2D())
model.add(Dropout(0.3))
model.add(Conv2D(16, (3,3), 1, activation='relu',kernel_regularizer=l2(0.05)))
model.add(MaxPooling2D())
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dense(4, activation='softmax'))
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
checkpoint = ModelCheckpoint("efficientnetB0_new.keras", monitor="val_accuracy", save_best_only=True, verbose=1)
# Reduce learning rate when the validation accuracy plateaus
reduce_lr = ReduceLROnPlateau(monitor='val_accuracy', factor=0.1, patience=2, min_delta=0.0001, verbose=1)
# Compile the model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
Adam (Adaptive Moment Estimation)¶
Adam (Adaptive Moment Estimation)¶
Adam est un algorithme d'optimisation largement utilisé pour entraîner les réseaux de neurones. Il combine les avantages de la descente de gradient avec momentum et de RMSProp. Voici comment il fonctionne :
Descente de Gradient :
- La descente de gradient est une méthode pour ajuster les paramètres d'un modèle afin de minimiser la fonction de perte. On met à jour les paramètres en suivant la pente de la fonction de perte.
Concepts de base d'Adam :
- Gradients : Les dérivées de la fonction de perte par rapport aux paramètres, indiquant la direction et la vitesse des ajustements.
- Moments : Adam utilise deux types de moments :
- Premier moment (moyenne des gradients) : $$ m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t $$
- Second moment (moyenne des carrés des gradients) : $$ v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 $$
Correction des biais :
- Adam corrige les biais initiaux dans les moments : $$ \hat{m}_t = \frac{m_t}{1 - \beta_1^t} $$ $$ \hat{v}_t = \frac{v_t}{1 - \beta_2^t} $$
Mise à jour des paramètres :
- Les paramètres sont mis à jour comme suit : $$ \theta_t = \theta_{t-1} - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} $$ où $$ \eta $$ est le taux d'apprentissage et $$ \epsilon $$ est un petit terme pour éviter la division par zéro.
Pourquoi Adam est efficace ?¶
- Adaptatif : Adam ajuste le taux d'apprentissage pour chaque paramètre individuellement.
- Moments : La combinaison des moments permet des mises à jour plus stables et efficaces.
- Stabilité : Les corrections de biais et l'utilisation des moments rendent Adam robuste pour une large gamme de problèmes.
Categorical Crossentropy¶
La fonction de perte Categorical Crossentropy est utilisée pour les problèmes de classification multi-classes. Elle mesure la différence entre deux distributions de probabilité : la distribution prédite par le modèle et la distribution réelle (ou cible).
Formule :
- Pour une seule instance, la categorical crossentropy est donnée par : $$ L = -\sum_{i=1}^N y_i \log(p_i) $$ où $$ y_i $$ est la valeur réelle (0 ou 1) pour la classe $$ p_i $$ est la probabilité prédite pour la classe , et $$ N $$ est le nombre total de classes.
Interprétation :
- Si le modèle est correct, la probabilité assignée à la classe réelle sera 1, et les autres seront 0. La perte sera alors nulle.
- Si le modèle est incorrect, la perte augmente en fonction de l'écart entre les prédictions et la vérité.
Utilisation :
- Categorical Crossentropy est souvent utilisée avec une couche de sortie softmax dans les réseaux de neurones, où la sortie est un vecteur de probabilités.
En résumé, Adam est une méthode d'optimisation adaptative et puissante, tandis que Categorical Crossentropy est une fonction de perte efficace pour mesurer la performance des modèles de classification multi-classes.
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 254, 254, 16) 448
batch_normalization (Batch (None, 254, 254, 16) 64
Normalization)
max_pooling2d (MaxPooling2 (None, 127, 127, 16) 0
D)
conv2d_1 (Conv2D) (None, 125, 125, 32) 4640
batch_normalization_1 (Bat (None, 125, 125, 32) 128
chNormalization)
max_pooling2d_1 (MaxPoolin (None, 62, 62, 32) 0
g2D)
dropout (Dropout) (None, 62, 62, 32) 0
conv2d_2 (Conv2D) (None, 60, 60, 16) 4624
max_pooling2d_2 (MaxPoolin (None, 30, 30, 16) 0
g2D)
dropout_1 (Dropout) (None, 30, 30, 16) 0
flatten (Flatten) (None, 14400) 0
dense (Dense) (None, 256) 3686656
dense_1 (Dense) (None, 4) 1028
=================================================================
Total params: 3697588 (14.11 MB)
Trainable params: 3697492 (14.10 MB)
Non-trainable params: 96 (384.00 Byte)
_________________________________________________________________
Couches Convolutionnelles¶
Les couches convolutionnelles sont des blocs de base dans les réseaux de neurones convolutifs (CNN). Elles sont efficaces pour extraire des caractéristiques spatiales des données d'entrée, telles que des images. Voici comment elles fonctionnent :
Convolution : La convolution applique des filtres (aussi appelés noyaux) sur l'image d'entrée pour extraire des informations locales. Ces filtres se déplacent sur l'image et effectuent des opérations de multiplication et d'agrégation, produisant des cartes de caractéristiques.
Padding : Le padding est souvent utilisé pour conserver la taille spatiale de l'image d'entrée après la convolution. Il consiste à ajouter des zéros autour des bords de l'image.
Stride : Le stride contrôle le nombre de pixels de décalage entre chaque application du filtre lors de la convolution. Il affecte la taille spatiale de la sortie.
Pooling : Après la convolution, une opération de pooling est souvent effectuée pour réduire la dimensionnalité spatiale des cartes de caractéristiques, tout en préservant les informations les plus importantes. Le pooling peut être de type max-pooling ou average-pooling.
Normalisation par Lots (Batch Normalization)¶
La normalisation par lots est une technique utilisée pour accélérer et stabiliser l'entraînement des réseaux de neurones en normalisant les activations de chaque couche. Voici comment elle fonctionne :
Normalisation : Pour chaque mini-lot d'entraînement, la normalisation par lots calcule la moyenne et l'écart-type des activations de chaque neurone. Ensuite, elle normalise ces activations en soustrayant la moyenne et en divisant par l'écart-type, puis en appliquant une transformation affine.
Stabilisation de l'entraînement : La normalisation par lots réduit le problème du décalage covariant en normalisant les activations à chaque couche. Cela aide à stabiliser les gradients pendant l'entraînement, ce qui permet d'utiliser des taux d'apprentissage plus élevés et d'accélérer la convergence.
Dropout¶
Le dropout est une technique de régularisation utilisée pour prévenir le surapprentissage dans les réseaux de neurones en désactivant aléatoirement un certain nombre de neurones pendant l'entraînement. Voici comment il fonctionne :
Désactivation aléatoire : Pendant chaque passage avant, chaque neurone d'une couche de dropout est désactivé avec une probabilité donnée (généralement 0.5). Cela équivaut à supprimer le neurone temporairement du réseau.
Régularisation : Le dropout force le réseau à apprendre des représentations plus robustes et générales en évitant de devenir trop dépendant de certains neurones ou de certaines caractéristiques. Cela réduit le risque de surapprentissage.
Pooling¶
Le pooling est une opération utilisée après la convolution pour réduire la dimensionnalité des cartes de caractéristiques tout en préservant les informations essentielles. Voici les types de pooling couramment utilisés :
Max-Pooling : Pour chaque zone de la carte de caractéristiques, le max-pooling sélectionne la valeur maximale, réduisant ainsi la taille spatiale de la carte tout en conservant les caractéristiques les plus importantes.
Average-Pooling : Le average-pooling calcule la moyenne des valeurs dans chaque zone de la carte de caractéristiques, ce qui réduit également la taille spatiale tout en conservant les informations générales.
Le pooling aide à rendre la représentation des caractéristiques plus invariants aux translations, réduisant ainsi le surapprentissage et la sensibilité aux petites variations dans les données.
log_directory = 'logs'
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir = log_directory)
history = model.fit(
train_images, train_labels_one_hot,
epochs=50,
validation_data=(val_images, val_labels_one_hot),
callbacks=[tensorboard_callback],
verbose=1
)
Epoch 1/50 72/72 [==============================] - 95s 1s/step - loss: 4.4358 - accuracy: 0.5829 - val_loss: 2.9983 - val_accuracy: 0.4859 Epoch 2/50 72/72 [==============================] - 85s 1s/step - loss: 2.0852 - accuracy: 0.8160 - val_loss: 2.5226 - val_accuracy: 0.5035 Epoch 3/50 72/72 [==============================] - 79s 1s/step - loss: 1.5872 - accuracy: 0.8893 - val_loss: 1.9295 - val_accuracy: 0.6837 Epoch 4/50 72/72 [==============================] - 77s 1s/step - loss: 1.2150 - accuracy: 0.9258 - val_loss: 1.3691 - val_accuracy: 0.8481 Epoch 5/50 72/72 [==============================] - 87s 1s/step - loss: 0.9600 - accuracy: 0.9336 - val_loss: 1.1569 - val_accuracy: 0.8233 Epoch 6/50 72/72 [==============================] - 84s 1s/step - loss: 0.7442 - accuracy: 0.9562 - val_loss: 0.9671 - val_accuracy: 0.8551 Epoch 7/50 72/72 [==============================] - 78s 1s/step - loss: 0.6091 - accuracy: 0.9570 - val_loss: 0.9075 - val_accuracy: 0.8339 Epoch 8/50 72/72 [==============================] - 77s 1s/step - loss: 0.5040 - accuracy: 0.9544 - val_loss: 0.7741 - val_accuracy: 0.8763 Epoch 9/50 72/72 [==============================] - 78s 1s/step - loss: 0.3776 - accuracy: 0.9796 - val_loss: 0.6892 - val_accuracy: 0.8781 Epoch 10/50 72/72 [==============================] - 79s 1s/step - loss: 0.3034 - accuracy: 0.9835 - val_loss: 0.6653 - val_accuracy: 0.8693 Epoch 11/50 72/72 [==============================] - 78s 1s/step - loss: 0.3141 - accuracy: 0.9670 - val_loss: 0.7060 - val_accuracy: 0.8410 Epoch 12/50 72/72 [==============================] - 82s 1s/step - loss: 0.2415 - accuracy: 0.9774 - val_loss: 0.5955 - val_accuracy: 0.8975 Epoch 13/50 72/72 [==============================] - 86s 1s/step - loss: 0.1949 - accuracy: 0.9865 - val_loss: 0.5801 - val_accuracy: 0.8816 Epoch 14/50 72/72 [==============================] - 87s 1s/step - loss: 0.1736 - accuracy: 0.9848 - val_loss: 0.5460 - val_accuracy: 0.9028 Epoch 15/50 72/72 [==============================] - 82s 1s/step - loss: 0.1427 - accuracy: 0.9918 - val_loss: 0.6048 - val_accuracy: 0.8445 Epoch 16/50 72/72 [==============================] - 81s 1s/step - loss: 0.1318 - accuracy: 0.9887 - val_loss: 0.6639 - val_accuracy: 0.8428 Epoch 17/50 72/72 [==============================] - 80s 1s/step - loss: 0.1400 - accuracy: 0.9835 - val_loss: 0.5374 - val_accuracy: 0.8993 Epoch 18/50 72/72 [==============================] - 81s 1s/step - loss: 0.1216 - accuracy: 0.9896 - val_loss: 0.4959 - val_accuracy: 0.8975 Epoch 19/50 72/72 [==============================] - 81s 1s/step - loss: 0.1351 - accuracy: 0.9835 - val_loss: 0.5914 - val_accuracy: 0.8710 Epoch 20/50 72/72 [==============================] - 79s 1s/step - loss: 0.1394 - accuracy: 0.9857 - val_loss: 0.6753 - val_accuracy: 0.8816 Epoch 21/50 72/72 [==============================] - 79s 1s/step - loss: 0.1160 - accuracy: 0.9887 - val_loss: 0.7039 - val_accuracy: 0.8604 Epoch 22/50 72/72 [==============================] - 80s 1s/step - loss: 0.1151 - accuracy: 0.9883 - val_loss: 0.6371 - val_accuracy: 0.8710 Epoch 23/50 72/72 [==============================] - 79s 1s/step - loss: 0.1349 - accuracy: 0.9852 - val_loss: 0.8789 - val_accuracy: 0.8799 Epoch 24/50 72/72 [==============================] - 79s 1s/step - loss: 0.1267 - accuracy: 0.9865 - val_loss: 0.5710 - val_accuracy: 0.8975 Epoch 25/50 72/72 [==============================] - 80s 1s/step - loss: 0.0982 - accuracy: 0.9926 - val_loss: 0.4860 - val_accuracy: 0.8975 Epoch 26/50 72/72 [==============================] - 80s 1s/step - loss: 0.1188 - accuracy: 0.9870 - val_loss: 0.5136 - val_accuracy: 0.8799 Epoch 27/50 72/72 [==============================] - 80s 1s/step - loss: 0.1144 - accuracy: 0.9861 - val_loss: 0.5487 - val_accuracy: 0.8834 Epoch 28/50 72/72 [==============================] - 82s 1s/step - loss: 0.0820 - accuracy: 0.9957 - val_loss: 0.4486 - val_accuracy: 0.9081 Epoch 29/50 72/72 [==============================] - 92s 1s/step - loss: 0.1050 - accuracy: 0.9874 - val_loss: 0.5429 - val_accuracy: 0.8940 Epoch 30/50 72/72 [==============================] - 80s 1s/step - loss: 0.0950 - accuracy: 0.9926 - val_loss: 0.7144 - val_accuracy: 0.8746 Epoch 31/50 72/72 [==============================] - 79s 1s/step - loss: 0.0884 - accuracy: 0.9926 - val_loss: 0.5008 - val_accuracy: 0.9081 Epoch 32/50 72/72 [==============================] - 79s 1s/step - loss: 0.0832 - accuracy: 0.9931 - val_loss: 0.6597 - val_accuracy: 0.9046 Epoch 33/50 72/72 [==============================] - 79s 1s/step - loss: 0.0851 - accuracy: 0.9926 - val_loss: 0.6576 - val_accuracy: 0.8781 Epoch 34/50 72/72 [==============================] - 78s 1s/step - loss: 0.0862 - accuracy: 0.9926 - val_loss: 0.6364 - val_accuracy: 0.8922 Epoch 35/50 72/72 [==============================] - 80s 1s/step - loss: 0.0906 - accuracy: 0.9887 - val_loss: 0.6493 - val_accuracy: 0.8693 Epoch 36/50 72/72 [==============================] - 79s 1s/step - loss: 0.0961 - accuracy: 0.9896 - val_loss: 0.7943 - val_accuracy: 0.8675 Epoch 37/50 72/72 [==============================] - 81s 1s/step - loss: 0.0908 - accuracy: 0.9909 - val_loss: 0.6715 - val_accuracy: 0.8675 Epoch 38/50 72/72 [==============================] - 80s 1s/step - loss: 0.1358 - accuracy: 0.9818 - val_loss: 0.6141 - val_accuracy: 0.8781 Epoch 39/50 72/72 [==============================] - 81s 1s/step - loss: 0.0949 - accuracy: 0.9918 - val_loss: 0.7711 - val_accuracy: 0.8551 Epoch 40/50 72/72 [==============================] - 82s 1s/step - loss: 0.1391 - accuracy: 0.9844 - val_loss: 9.5285 - val_accuracy: 0.2703 Epoch 41/50 72/72 [==============================] - 81s 1s/step - loss: 0.1729 - accuracy: 0.9761 - val_loss: 1.1094 - val_accuracy: 0.7898 Epoch 42/50 72/72 [==============================] - 82s 1s/step - loss: 0.1922 - accuracy: 0.9757 - val_loss: 0.4454 - val_accuracy: 0.9011 Epoch 43/50 72/72 [==============================] - 81s 1s/step - loss: 0.1060 - accuracy: 0.9974 - val_loss: 0.5128 - val_accuracy: 0.9046 Epoch 44/50 72/72 [==============================] - 82s 1s/step - loss: 0.0866 - accuracy: 0.9952 - val_loss: 0.4889 - val_accuracy: 0.8887 Epoch 45/50 72/72 [==============================] - 81s 1s/step - loss: 0.0698 - accuracy: 0.9961 - val_loss: 0.4932 - val_accuracy: 0.9028 Epoch 46/50 72/72 [==============================] - 82s 1s/step - loss: 0.0541 - accuracy: 1.0000 - val_loss: 0.4212 - val_accuracy: 0.8993 Epoch 47/50 72/72 [==============================] - 81s 1s/step - loss: 0.0457 - accuracy: 0.9991 - val_loss: 0.5001 - val_accuracy: 0.8869 Epoch 48/50 72/72 [==============================] - 81s 1s/step - loss: 0.0523 - accuracy: 0.9970 - val_loss: 0.3706 - val_accuracy: 0.9099 Epoch 49/50 72/72 [==============================] - 81s 1s/step - loss: 0.0456 - accuracy: 0.9987 - val_loss: 0.5786 - val_accuracy: 0.9011 Epoch 50/50 72/72 [==============================] - 83s 1s/step - loss: 0.0467 - accuracy: 0.9974 - val_loss: 0.4399 - val_accuracy: 0.8940
Save the model¶
from tensorflow.keras.models import load_model
model.save(os.path.join('models', 'tumorClasifier.h5'))
F:\python_amour\Lib\site-packages\keras\src\engine\training.py:3000: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.
saving_api.save_model(
model = load_model(os.path.join('models', 'tumorClasifier.h5'))
plotting performance whilst training¶
fig = plt.figure()
plt.plot(history.history["loss"], color = "teal", label="loss")
plt.plot(history.history["val_loss"], color = "orange", label="validation_loss")
fig.suptitle("loss", fontsize=20)
plt.legend(loc="upper left")
plt.show()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[29], line 2 1 fig = plt.figure() ----> 2 plt.plot(history.history["loss"], color = "teal", label="loss") 3 plt.plot(history.history["val_loss"], color = "orange", label="validation_loss") 4 fig.suptitle("loss", fontsize=20) NameError: name 'history' is not defined
<Figure size 640x480 with 0 Axes>
fig = plt.figure()
plt.plot(history.history['accuracy'], color='teal', label ='accuracy')
plt.plot(history.history['val_accuracy'], color = 'orange', label='val_accuracy')
fig.suptitle('Accuracy', fontsize=20)
plt.legend(loc="upper left")
plt.show()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[30], line 2 1 fig = plt.figure() ----> 2 plt.plot(history.history['accuracy'], color='teal', label ='accuracy') 3 plt.plot(history.history['val_accuracy'], color = 'orange', label='val_accuracy') 4 fig.suptitle('Accuracy', fontsize=20) NameError: name 'history' is not defined
<Figure size 640x480 with 0 Axes>
Evaluating performance¶
from tensorflow.keras.metrics import Precision, Recall, Accuracy
precision = Precision()
recall = Recall()
accuracy = Accuracy()
for i, batch in enumerate(test.as_numpy_iterator()):
x,y = batch
ypred = model.predict(x)
precision.update_state(test_labels_one_hot[32*i:32*(i+1)],ypred)
recall.update_state(test_labels_one_hot[32*i:32*(i+1)],ypred)
accuracy.update_state(test_labels_one_hot[32*i:32*(i+1)],ypred)
1/1 [==============================] - 1s 779ms/step 1/1 [==============================] - 1s 571ms/step 1/1 [==============================] - 0s 328ms/step 1/1 [==============================] - 0s 344ms/step 1/1 [==============================] - 0s 334ms/step 1/1 [==============================] - 0s 313ms/step 1/1 [==============================] - 0s 297ms/step 1/1 [==============================] - 0s 344ms/step 1/1 [==============================] - 0s 312ms/step 1/1 [==============================] - 0s 313ms/step 1/1 [==============================] - 0s 297ms/step 1/1 [==============================] - 0s 297ms/step 1/1 [==============================] - 0s 250ms/step
precision_value = precision.result() * 100
recall_value = recall.result() * 100
accuracy_value = accuracy.result() * 100
print("Precision: {:.2f}%".format(precision_value))
print("Recall: {:.2f}%".format(recall_value))
print("Accuracy: {:.2f}%".format(accuracy_value))
Precision: 26.90% Recall: 26.90% Accuracy: 0.70%
what is hard to classify ?¶
ypred = model.predict(test_images)
13/13 [==============================] - 4s 309ms/step
y_pred_classes = np.argmax(ypred,axis = 1)
# Convert validation observations to one hot vectors
y_true = np.argmax(test_labels_one_hot,axis = 1)
# compute the confusion matrix
confusion_mtx = tf.math.confusion_matrix(y_true, y_pred_classes)
def plot_confusion_matrix(confusion_mtx, classes):
fig = go.Figure(data=go.Heatmap(
z=confusion_mtx,
x=classes,
y=classes,
colorscale='Reds',
hoverongaps=False,
colorbar=dict(title='Count')
))
# Add text annotations
for i in range(len(classes)):
for j in range(len(classes)):
fig.add_annotation(
x=classes[j],
y=classes[i],
text=str(confusion_mtx[i][j].numpy()), # Extracting value from TensorFlow tensor
showarrow=False
)
# Extract maximum value from the TensorFlow tensor
zmax = tf.reduce_max(confusion_mtx).numpy()
fig.update_layout(
title='Confusion Matrix',
xaxis_title='Predicted label',
yaxis_title='True label',
coloraxis=dict(cmin=0, cmax=zmax) # Setting color axis range
)
fig.show()
plot_confusion_matrix(confusion_mtx,classes)
Dimensionality reduction¶
full_ds = np.concatenate((train_images, val_images, test_images))
ypred = model.predict(full_ds)
102/102 [==============================] - 30s 289ms/step
labels_dict= dict(zip([i for i in range (4)], classes))
max_indices = np.argmax(ypred, axis=1)
# Map the indices to class names
predicted_classes = [idx for idx in max_indices]
predicted_classes = [labels_dict[x] for x in predicted_classes]
Get embeddings¶
Les embeddings dans un réseau de neurones convolutionnel (CNN) sont des représentations vectorielles d'entrées de données qui capturent les relations et les similarités entre les différentes caractéristiques des données en les projetant dans un espace vectoriel de dimension réduite. Voici comment les embeddings sont généralement utilisés dans un CNN :
Embedding Layer : Dans un CNN, l'embedding est souvent introduit par une couche d'embedding. Cette couche est initialement utilisée dans le traitement de texte pour convertir des entiers (représentant les mots) en vecteurs de valeurs réelles. Dans un contexte CNN, cette couche peut être utilisée pour encoder des caractéristiques d'entrée spécifiques (comme des pixels dans une image) en vecteurs d'embedding.
Convolutional Layers : Une fois que les données ont été transformées en embeddings, elles sont passées à travers des couches convolutionnelles. Ces couches effectuent des opérations de convolution sur les embeddings, en appliquant des filtres pour extraire des caractéristiques importantes des données. Les filtres peuvent détecter des motifs spécifiques dans les embeddings, tels que des bords, des formes ou d'autres structures significatives.
Pooling Layers : Après les couches de convolution, des couches de pooling sont souvent utilisées pour réduire la dimensionnalité des embeddings. Cela permet de réduire la complexité du modèle et d'extraire les caractéristiques les plus importantes de manière plus efficace. Les opérations de pooling, comme le max-pooling ou le average-pooling, agrègent les informations des embeddings sur des régions spécifiques, réduisant ainsi leur taille tout en préservant les caractéristiques importantes.
Fully Connected Layers : Les embeddings résultants sont ensuite aplatis et passés à travers des couches entièrement connectées, où des opérations de classification ou de régression peuvent être effectuées. Ces couches prennent les embeddings extraits par les couches convolutionnelles et les utilisent pour prédire des étiquettes ou des valeurs de sortie.
En résumé, les embeddings dans un CNN jouent un rôle crucial en transformant les données d'entrée en représentations vectorielles significatives, en extrayant des caractéristiques importantes à l'aide de couches convolutionnelles et en facilitant la prise de décision finale à travers des couches entièrement connectées.
from tensorflow.keras.models import Model
embedding_model = Model(inputs=model.input,
outputs=model.get_layer('dense').output) # Specify the layer
embeddings = embedding_model.predict(full_ds) # X is your input data
102/102 [==============================] - 39s 367ms/step
from sklearn.manifold import TSNE
Algorithme t-SNE¶
L'algorithme t-SNE (t-Distributed Stochastic Neighbor Embedding) est une méthode de réduction de dimensionnalité non linéaire particulièrement adaptée pour la visualisation de données de haute dimension. Il convertit les distances entre les points de données en probabilités, puis minimise la divergence entre ces probabilités dans les espaces de haute et basse dimension.
Étapes de l'algorithme t-SNE¶
Calcul des probabilités conditionnelles en haute dimension
Pour chaque paire de points $$ (x_i, x_j) $$ dans l'espace de haute dimension, nous calculons les probabilités conditionnelles $$ p_{j|i} $$ qui représentent la similarité des points. Ces probabilités sont définies comme suit :
$$ p_{j|i} = \frac{\exp(-\|x_i - x_j\|^2 / 2\sigma_i^2)}{\sum_{k \neq i} \exp(-\|x_i - x_k\|^2 / 2\sigma_i^2)} $$où $$ \sigma_i $$ est une échelle de densité locale qui est réglée de manière à ce que l'entropie de la distribution $$p_{j|i} $$ soit constante.
Symétrisation des probabilités
Les probabilités symétriques $$ p_{ij} $$ sont ensuite calculées en prenant la moyenne des probabilités conditionnelles :
$$ p_{ij} = \frac{p_{j|i} + p_{i|j}}{2N} $$où ( N ) est le nombre total de points de données.
Calcul des probabilités en basse dimension
De même, dans l'espace de basse dimension, nous définissons une distribution ( q_{ij} ) basée sur une distribution de Student à une seule dimension :
$$ q_{ij} = \frac{(1 + \|y_i - y_j\|^2)^{-1}}{\sum_{k \neq l} (1 + \|y_k - y_l\|^2)^{-1}} $$où ( y_i ) et ( y_j ) sont les points correspondants à ( x_i ) et ( x_j ) dans l'espace de basse dimension.
Minimisation de la divergence de Kullback-Leibler
L'objectif de t-SNE est de minimiser la divergence de Kullback-Leibler (KL) entre les distributions ( P ) et ( Q ) :
$$ KL(P \| Q) = \sum_{i \neq j} p_{ij} \log \frac{p_{ij}}{q_{ij}} $$Cette minimisation est effectuée par une méthode de gradient descendant.
Résumé¶
L'algorithme t-SNE procède en quatre étapes principales :
- Calcul des probabilités conditionnelles en haute dimension $$ p_{j|i} $$.
- Symétrisation pour obtenir $$ p_{ij} $$.
- Calcul des probabilités en basse dimension $$ q_{ij} $$ à l'aide de la distribution de Student.
- Minimisation de la divergence KL pour aligner les distributions P et Q.
Ce processus permet de représenter fidèlement la structure locale et globale des données de haute dimension dans un espace de basse dimension, souvent de 2 ou 3 dimensions, facilitant ainsi la visualisation et l'interprétation des données.
Entropie¶
L'entropie est une mesure de l'incertitude ou du désordre dans une distribution de probabilité. En termes simples, elle quantifie le niveau de surprise associé à des événements imprévus dans une distribution.
Entropie en théorie de l'information¶
Dans le contexte de la théorie de l'information, l'entropie H d'une distribution de probabilité discrète P est définie comme suit :
$$ H(P) = -\sum_{i} p_i \log(p_i) $$où $$ p_i $$ est la probabilité de l'événement i. L'entropie est maximisée lorsque toutes les probabilités $$ p_i $$ sont égales, ce qui correspond à un état de désordre maximal où chaque événement est également probable.
Entropie en t-SNE¶
Dans l'algorithme t-SNE, l'entropie joue un rôle important dans le calcul des probabilités conditionnelles. Pour chaque point de donnée $$ x_i $$, nous ajustons $$ \sigma_i $$ de manière à ce que l'entropie de la distribution $$ p_{j|i} $$ soit constante. Cela permet de maintenir une échelle de densité locale appropriée pour chaque point, en s'assurant que les points voisins proches ont des probabilités conditionnelles plus élevées.
L'entropie de la distribution $$ p_{j|i} $$ est définie comme :
$$ H_i = -\sum_{j} p_{j|i} \log(p_{j|i}) $$En réglant $$ \sigma_i $$ pour maintenir l'entropie $$ H_i $$ à une valeur cible, t-SNE peut adapter les échelles locales et représenter les structures de données de manière plus fidèle.
Importance de l'entropie¶
L'entropie est essentielle pour comprendre et quantifier la quantité d'information ou d'incertitude dans une distribution de données. Dans le cadre de t-SNE, elle garantit que les relations locales entre les points de données sont correctement capturées, ce qui permet de produire des visualisations de haute qualité des données de haute dimension.
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(embeddings)
colors = ['#001219', '#005F73', '#0A9396', '#FF5733', '#FFBD69', '#E9D8A6', '#FF931E', '#CA6702', '#D62828', '#9B2226']
plt.figure(figsize=(16, 14))
for i, class_label in enumerate(np.unique(predicted_classes)):
indices = np.where(np.array(predicted_classes) == class_label)[0]
x = X_tsne[indices, 0]
y = X_tsne[indices, 1]
# Stretch the circles by manipulating the data
x_stretched = x * 2 # Example stretching factor, adjust as needed
y_stretched = y * 1 # Adjust the stretching factor for the y-direction if needed
# Add a black square behind the text annotation
plt.scatter(np.mean(x_stretched), np.mean(y_stretched), color='white', s=2000, zorder=10)
# Add the class label in the center of the cluster with white color
plt.text(np.mean(x_stretched), np.mean(y_stretched), str(class_label), fontsize=10, fontweight='bold', ha='center', va='center', zorder=11, color='black')
plt.scatter(x_stretched, y_stretched, label=str(class_label), color=colors[i], s=100) # Set the size of the scatter points
plt.xlabel('t-sne Component 1')
plt.ylabel('t-sne Component 2')
plt.title('Scatterplot of Class Embeddings')
plt.legend(title='Number')
plt.savefig('classes_t-sne_2d.png')
plt.show()
tsne3d = TSNE(n_components=3, random_state=42)
X_tsne3d = tsne3d.fit_transform(embeddings)
data = []
for i, class_label in enumerate(np.unique(predicted_classes)):
indices = np.where(np.array(predicted_classes) == class_label)[0]
x = X_tsne3d[indices, 0]
y = X_tsne3d[indices, 1]
z = X_tsne3d[indices, 2]
# Scatter plot for each class
data.append(go.Scatter3d(
x=x,
y=y,
z=z,
mode='markers',
name=str(class_label),
marker=dict(
size=6,
color=colors[i],
opacity=0.8
)
))
layout = go.Layout(
scene=dict(
xaxis=dict(title='t-sne Component 1'),
yaxis=dict(title='t-sne Component 2'),
zaxis=dict(title='t-sne Component 3')
),
title='Scatterplot of Class Embeddings'
)
fig = go.Figure(data=data, layout=layout)
fig.write_html("3D mnist.html")
fig.show()
Interprétation des composantes t-SNE¶
L'algorithme t-SNE est souvent utilisé pour visualiser des données de haute dimension en réduisant celles-ci à deux ou trois dimensions. L'interprétation des composantes t-SNE doit être faite avec soin, car bien que t-SNE conserve les structures locales, il ne conserve pas nécessairement les distances globales ni les échelles.
Points clés pour l'interprétation¶
Structure locale :
- Les points proches les uns des autres dans la visualisation t-SNE sont également proches dans l'espace de haute dimension.
- Les clusters ou groupes de points observés en t-SNE représentent des groupes de données similaires dans l'espace original.
Distances globales :
- Les distances entre des clusters éloignés peuvent ne pas être significatives. En d'autres termes, la distance entre deux clusters dans la visualisation de basse dimension ne reflète pas nécessairement la distance dans l'espace de haute dimension.
- La technique met davantage l'accent sur la préservation des structures locales plutôt que sur les relations globales.
Axes t-SNE :
- Les axes résultants des composantes t-SNE (souvent notés t-SNE 1, t-SNE 2, etc.) n'ont pas de signification inhérente. Ils ne correspondent pas à des variables spécifiques des données d'origine.
- La direction et l'échelle des axes peuvent varier d'une exécution à l'autre en raison de la nature stochastique de l'algorithme.
Clusters et sous-clusters :
- La présence de clusters distincts peut indiquer des sous-groupes naturels ou des classes dans les données.
- Les sous-clusters à l'intérieur de plus grands clusters peuvent représenter des niveaux de granularité supplémentaires.
Densité des points :
- La densité des points dans certaines régions de la visualisation peut indiquer des zones de haute densité dans les données d'origine.
- Les zones plus dispersées peuvent représenter des régions de moindre densité.
Exemple d'interprétation¶
Imaginons que nous utilisons t-SNE pour visualiser un ensemble de données sur des images de chiffres manuscrits (par exemple, le jeu de données MNIST). Voici quelques points d'interprétation possibles :
- Clusters distincts : Nous voyons des clusters distincts pour chaque chiffre (0, 1, 2, etc.). Cela signifie que les images de chaque chiffre sont similaires entre elles et distinctes des autres chiffres.
- Proximité des clusters : Les chiffres 1 et 7 peuvent être proches l'un de l'autre car ils ont des formes similaires, ce qui conduit à une similarité dans l'espace de caractéristiques.
- Sous-clusters : À l'intérieur du cluster représentant le chiffre 1, nous pourrions voir des sous-clusters correspondant à différentes écritures du chiffre 1 (par exemple, des 1 écrits avec ou sans empattement).
Précautions¶
- Variabilité des résultats : Chaque exécution de t-SNE peut donner des résultats légèrement différents en raison de sa nature stochastique. Il est important de vérifier la stabilité des clusters observés sur plusieurs exécutions.
- Hyperparamètres : Les résultats de t-SNE peuvent être sensibles aux hyperparamètres (comme le nombre de voisins perplexité). Une bonne pratique consiste à expérimenter avec différents paramètres pour trouver ceux qui capturent le mieux la structure des données.
En résumé, t-SNE est un outil puissant pour la visualisation des données de haute dimension, mais son interprétation nécessite de comprendre ses limites et de se concentrer principalement sur les structures locales plutôt que sur les distances globales.
last_conv_layer = model.get_layer('conv2d_2')
classifier_layer_name = model.get_layer('dense_1')
from tensorflow.keras.models import Model
import tensorflow as tf
import numpy as np
import cv2
class GradCAM:
def __init__(self, model, classIdx, layerName=None):
# store the model, the class index used to measure the class
# activation map, and the layer to be used when visualizing
# the class activation map
self.model = model
self.classIdx = classIdx
self.layerName = layerName
# if the layer name is None, attempt to automatically find
# the target output layer
if self.layerName is None:
self.layerName = self.find_target_layer()
def find_target_layer(self):
# attempt to find the final convolutional layer in the network
# by looping over the layers of the network in reverse order
for layer in reversed(self.model.layers):
# check to see if the layer has a 4D output
if len(layer.output_shape) == 4:
return layer.name
# otherwise, we could not find a 4D layer so the GradCAM
# algorithm cannot be applied
raise ValueError("Could not find 4D layer. Cannot apply GradCAM.")
def compute_heatmap(self, image, eps=1e-8):
# construct our gradient model by supplying (1) the inputs
# to our pre-trained model, (2) the output of the (presumably)
# final 4D layer in the network, and (3) the output of the
# softmax activations from the model
gradModel = Model(
inputs=[self.model.inputs],
outputs=[self.model.get_layer(self.layerName).output, self.model.output])
# record operations for automatic differentiation
with tf.GradientTape() as tape:
# cast the image tensor to a float-32 data type, pass the
# image through the gradient model, and grab the loss
# associated with the specific class index
inputs = tf.cast(image, tf.float32)
(convOutputs, predictions) = gradModel(inputs)
loss = predictions[:, tf.argmax(predictions[0])]
# use automatic differentiation to compute the gradients
grads = tape.gradient(loss, convOutputs)
# compute the guided gradients
castConvOutputs = tf.cast(convOutputs > 0, "float32")
castGrads = tf.cast(grads > 0, "float32")
guidedGrads = castConvOutputs * castGrads * grads
# the convolution and guided gradients have a batch dimension
# (which we don't need) so let's grab the volume itself and
# discard the batch
convOutputs = convOutputs[0]
guidedGrads = guidedGrads[0]
# compute the average of the gradient values, and using them
# as weights, compute the ponderation of the filters with
# respect to the weights
weights = tf.reduce_mean(guidedGrads, axis=(0, 1))
cam = tf.reduce_sum(tf.multiply(weights, convOutputs), axis=-1)
# grab the spatial dimensions of the input image and resize
# the output class activation map to match the input image
# dimensions
(w, h) = (image.shape[2], image.shape[1])
heatmap = cv2.resize(cam.numpy(), (w, h))
# normalize the heatmap such that all values lie in the range
# [0, 1], scale the resulting values to the range [0, 255],
# and then convert to an unsigned 8-bit integer
numer = heatmap - np.min(heatmap)
denom = (heatmap.max() - heatmap.min()) + eps
heatmap = numer / denom
heatmap = (heatmap * 255).astype("uint8")
# return the resulting heatmap to the calling function
return heatmap
def overlay_heatmap(self, heatmap, image, alpha=0.5,
colormap=cv2.COLORMAP_VIRIDIS):
# apply the supplied color map to the heatmap and then
# overlay the heatmap on the input image
heatmap = cv2.applyColorMap(heatmap, colormap)
output = cv2.addWeighted(image, alpha, heatmap, 1 - alpha, 0)
# return a 2-tuple of the color mapped heatmap and the output,
# overlaid image
return (heatmap, output)
# Create the output directory
output_dir = "output_images"
os.makedirs(output_dir, exist_ok=True)
# Collect test images and labels
test_images = []
test_labels = []
for images, labels in test:
test_images.append(images.numpy())
test_labels.append(labels.numpy())
test_images = np.concatenate(test_images)
# Ensure correct input shape
model_input_shape = model.input_shape[1:3] # Typically (height, width)
# Process the first 10 images
for row, img in enumerate(test_images[:10]):
# Resize image to match model's expected input shape
image_resized = cv2.resize(img, (model_input_shape[1], model_input_shape[0])) # OpenCV uses (width, height)
# Normalize the pixel values
image_resized = image_resized / 255.0
# Add a batch dimension
image_resized = np.expand_dims(image_resized, axis=0)
# Predict using the model
preds = model.predict(image_resized)
i = np.argmax(preds[0])
class_name = classes[i]
probability = np.max(preds)
# Convert image to RGB for plotting
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Calculate CAM
icam = GradCAM(model, i, 'conv2d_1') # Update with the correct layer name for your model
heatmap = icam.compute_heatmap(image_resized)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0])) # Resize heatmap to match original image
# Resize original image to match heatmap
img_resized = cv2.resize(img_rgb, (heatmap.shape[1], heatmap.shape[0]))
# Convert the heatmap to uint8
heatmap = (heatmap * 255).astype(np.uint8)
# Apply colormap to heatmap
heatmap_colored = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
# Ensure img_resized is in uint8 format
if img_resized.dtype != np.uint8:
img_resized = (img_resized * 255).astype(np.uint8)
# Overlay heatmap on the original image
output = cv2.addWeighted(img_resized, 0.5, heatmap_colored, 0.5, 0)
# Plot image, heatmap, and overlay
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.imshow(img_rgb)
plt.title(f'Class: {class_name} ({probability:.2f})')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(heatmap_colored)
plt.title('Class Activation Heatmap')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(output)
plt.title('CAM Overlay')
plt.axis('off')
# Save the row of images as PNG
plt.savefig(os.path.join(output_dir, f'row_{row}.png'))
plt.show()
plt.close()
print("Images exported successfully.")
1/1 [==============================] - 0s 50ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 31ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 29ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 35ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 37ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 36ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 40ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 35ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 35ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
1/1 [==============================] - 0s 30ms/step
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Images exported successfully.